// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "status.h"

#ifdef _WIN32
#include "win32port.h"
#else
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <cinttypes>
#endif

#include <stdarg.h>
#include <stdlib.h>

#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif

#include "debug_flags.h"

using namespace std;

StatusPrinter::StatusPrinter(const BuildConfig& config)
        : config_(config), started_edges_(0), finished_edges_(0), total_edges_(0),
            running_edges_(0), progress_status_format_(NULL),
            current_rate_(config.parallelism) {
    // Don't do anything fancy in verbose mode.
    if (config_.verbosity != BuildConfig::NORMAL)
        printer_.set_smart_terminal(false);

    progress_status_format_ = getenv("NINJA_STATUS");
    if (!progress_status_format_)
        progress_status_format_ = "[%f/%t] ";
}

void StatusPrinter::EdgeAddedToPlan(const Edge* edge) {
    ++total_edges_;

    // Do we know how long did this edge take last time?
    if (edge->prev_elapsed_time_millis != -1) {
        ++eta_predictable_edges_total_;
        ++eta_predictable_edges_remaining_;
        eta_predictable_cpu_time_total_millis_ += edge->prev_elapsed_time_millis;
        eta_predictable_cpu_time_remaining_millis_ +=
                edge->prev_elapsed_time_millis;
    } else
        ++eta_unpredictable_edges_remaining_;
}

void StatusPrinter::EdgeRemovedFromPlan(const Edge* edge) {
    --total_edges_;

    // Do we know how long did this edge take last time?
    if (edge->prev_elapsed_time_millis != -1) {
        --eta_predictable_edges_total_;
        --eta_predictable_edges_remaining_;
        eta_predictable_cpu_time_total_millis_ -= edge->prev_elapsed_time_millis;
        eta_predictable_cpu_time_remaining_millis_ -=
                edge->prev_elapsed_time_millis;
    } else
        --eta_unpredictable_edges_remaining_;
}

void StatusPrinter::BuildEdgeStarted(const Edge* edge,
                                                                          int64_t start_time_millis) {
    ++started_edges_;
    ++running_edges_;
    time_millis_ = start_time_millis;

    if (edge->use_console() || printer_.is_smart_terminal())
        PrintStatus(edge, start_time_millis);

    if (edge->use_console())
        printer_.SetConsoleLocked(true);
}

void StatusPrinter::RecalculateProgressPrediction() {
    time_predicted_percentage_ = 0.0;

    // Sometimes, the previous and actual times may be wildly different.
    // For example, the previous build may have been fully recovered from ccache,
    // so it was blazing fast, while the new build no longer gets hits from ccache
    // for whatever reason, so it actually compiles code, which takes much longer.
    // We should detect such cases, and avoid using "wrong" previous times.

    // Note that we will only use the previous times if there are edges with
    // previous time knowledge remaining.
    bool use_previous_times = eta_predictable_edges_remaining_ &&
                                                        eta_predictable_cpu_time_remaining_millis_;

    // Iff we have sufficient statistical information for the current run,
    // that is, if we have took at least 15 sec AND finished at least 5% of edges,
    // we can check whether our performance so far matches the previous one.
    if (use_previous_times && total_edges_ && finished_edges_ &&
        (time_millis_ >= 15 * 1e3) &&
        (((double)finished_edges_ / total_edges_) >= 0.05)) {
        // Over the edges we've just run, how long did they take on average?
        double actual_average_cpu_time_millis =
            (double)cpu_time_millis_ / finished_edges_;
        // What is the previous average, for the edges with such knowledge?
        double previous_average_cpu_time_millis =
            (double)eta_predictable_cpu_time_total_millis_ /
            eta_predictable_edges_total_;

        double ratio = std::max(previous_average_cpu_time_millis,
            actual_average_cpu_time_millis) /
            std::min(previous_average_cpu_time_millis,
            actual_average_cpu_time_millis);

        // Let's say that the average times should differ by less than 10x
        use_previous_times = ratio < 10;
    }

    int edges_with_known_runtime = finished_edges_;
    if (use_previous_times)
        edges_with_known_runtime += eta_predictable_edges_remaining_;
    if (edges_with_known_runtime == 0)
        return;

    int edges_with_unknown_runtime = use_previous_times
        ? eta_unpredictable_edges_remaining_
        : (total_edges_ - finished_edges_);

    // Given the time elapsed on the edges we've just run,
    // and the runtime of the edges for which we know previous runtime,
    // what's the edge's average runtime?
    int64_t edges_known_runtime_total_millis = cpu_time_millis_;
    if (use_previous_times)
        edges_known_runtime_total_millis +=
            eta_predictable_cpu_time_remaining_millis_;

    double average_cpu_time_millis =
        (double)edges_known_runtime_total_millis / edges_with_known_runtime;

    // For the edges for which we do not have the previous runtime,
    // let's assume that their average runtime is the same as for the other edges,
    // and we therefore can predict their remaining runtime.
    double unpredictable_cpu_time_remaining_millis =
            average_cpu_time_millis * edges_with_unknown_runtime;

    // And therefore we can predict the remaining and total runtimes.
    double total_cpu_time_remaining_millis =
        unpredictable_cpu_time_remaining_millis;
    if (use_previous_times)
        total_cpu_time_remaining_millis +=
            eta_predictable_cpu_time_remaining_millis_;
    double total_cpu_time_millis =
        cpu_time_millis_ + total_cpu_time_remaining_millis;
    if (total_cpu_time_millis == 0.0)
        return;

    // After that we can tell how much work we've completed, in time units.
    time_predicted_percentage_ = cpu_time_millis_ / total_cpu_time_millis;
}

void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t start_time_millis,
                                                                            int64_t end_time_millis, bool success,
                                                                            const string& output) {
    time_millis_ = end_time_millis;
    ++finished_edges_;

    int64_t elapsed = end_time_millis - start_time_millis;
    cpu_time_millis_ += elapsed;

    // Do we know how long did this edge take last time?
    if (edge->prev_elapsed_time_millis != -1) {
        --eta_predictable_edges_remaining_;
        eta_predictable_cpu_time_remaining_millis_ -=
            edge->prev_elapsed_time_millis;
    } else
        --eta_unpredictable_edges_remaining_;

    if (edge->use_console())
        printer_.SetConsoleLocked(false);

    if (config_.verbosity == BuildConfig::QUIET)
        return;

    if (!edge->use_console())
        PrintStatus(edge, end_time_millis);

    --running_edges_;

    // Print the command that is spewing before printing its output.
    if (!success) {
        string outputs;
        for (vector<Node*>::const_iterator o = edge->outputs_.begin();
                o != edge->outputs_.end(); ++o)
            outputs += (*o)->path() + " ";

        if (printer_.supports_color()) {
            printer_.PrintOnNewLine("\x1B[31m" "FAILED: " "\x1B[0m" + outputs + "\n");
        } else {
            printer_.PrintOnNewLine("FAILED: " + outputs + "\n");
        }
        printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n");
    }

    if (!output.empty()) {
        // ninja sets stdout and stderr of subprocesses to a pipe, to be able to
        // check if the output is empty. Some compilers, e.g. clang, check
        // isatty(stderr) to decide if they should print colored output.
        // To make it possible to use colored output with ninja, subprocesses should
        // be run with a flag that forces them to always print color escape codes.
        // To make sure these escape codes don't show up in a file if ninja's output
        // is piped to a file, ninja strips ansi escape codes again if it's not
        // writing to a |smart_terminal_|.
        // (Launching subprocesses in pseudo ttys doesn't work because there are
        // only a few hundred available on some systems, and ninja can launch
        // thousands of parallel compile commands.)
        string final_output;
        if (!printer_.supports_color())
            final_output = StripAnsiEscapeCodes(output);
        else
            final_output = output;

#ifdef _WIN32
    // Fix extra CR being added on Windows, writing out CR CR LF (#773)
    _setmode(_fileno(stdout), _O_BINARY);  // Begin Windows extra CR fix
#endif

    printer_.PrintOnNewLine(final_output);

#ifdef _WIN32
    _setmode(_fileno(stdout), _O_TEXT);  // End Windows extra CR fix
#endif
    }
}

void StatusPrinter::BuildLoadDyndeps() {
    // The DependencyScan calls EXPLAIN() to print lines explaining why
    // it considers a portion of the graph to be out of date.  Normally
    // this is done before the build starts, but our caller is about to
    // load a dyndep file during the build.  Doing so may generate more
    // explanation lines (via fprintf directly to stderr), but in an
    // interactive console the cursor is currently at the end of a status
    // line.  Start a new line so that the first explanation does not
    // append to the status line.  After the explanations are done a
    // new build status line will appear.
    if (g_explaining)
        printer_.PrintOnNewLine("");
}

void StatusPrinter::BuildStarted() {
    started_edges_ = 0;
    finished_edges_ = 0;
    running_edges_ = 0;
}

void StatusPrinter::BuildFinished() {
    printer_.SetConsoleLocked(false);
    printer_.PrintOnNewLine("");
}

string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
                                                                                      int64_t time_millis) const {
    string out;
    char buf[32];
    for (const char* s = progress_status_format; *s != '\0'; ++s) {
        if (*s == '%') {
            ++s;
            switch (*s) {
            case '%':
                out.push_back('%');
                break;

                // Started edges.
            case 's':
                snprintf(buf, sizeof(buf), "%d", started_edges_);
                out += buf;
                break;

                // Total edges.
            case 't':
                snprintf(buf, sizeof(buf), "%d", total_edges_);
                out += buf;
                break;

                // Running edges.
            case 'r': {
                snprintf(buf, sizeof(buf), "%d", running_edges_);
                out += buf;
                break;
            }

                // Unstarted edges.
            case 'u':
                snprintf(buf, sizeof(buf), "%d", total_edges_ - started_edges_);
                out += buf;
                break;

                // Finished edges.
            case 'f':
                snprintf(buf, sizeof(buf), "%d", finished_edges_);
                out += buf;
                break;

                // Overall finished edges per second.
            case 'o':
                SnprintfRate(finished_edges_ / (time_millis_ / 1e3), buf, "%.1f");
                out += buf;
                break;

                // Current rate, average over the last '-j' jobs.
            case 'c':
                current_rate_.UpdateRate(finished_edges_, time_millis_);
                SnprintfRate(current_rate_.rate(), buf, "%.1f");
                out += buf;
                break;

                // Percentage of edges completed
            case 'p': {
                int percent = 0;
                if (finished_edges_ != 0 && total_edges_ != 0)
                    percent = (100 * finished_edges_) / total_edges_;
                snprintf(buf, sizeof(buf), "%3i%%", percent);
                out += buf;
                break;
            }

#define FORMAT_TIME_HMMSS(t)                                                \
    "%" PRId64 ":%02" PRId64 ":%02" PRId64 "", (t) / 3600, ((t) % 3600) / 60, \
            (t) % 60
#define FORMAT_TIME_MMSS(t) "%02" PRId64 ":%02" PRId64 "", (t) / 60, (t) % 60

                // Wall time
            case 'e':  // elapsed, seconds
            case 'w':  // elapsed, human-readable
            case 'E':  // ETA, seconds
            case 'W':  // ETA, human-readable
            {
                double elapsed_sec = time_millis_ / 1e3;
                double eta_sec = -1;  // To be printed as "?".
                if (time_predicted_percentage_ != 0.0) {
                    // So, we know that we've spent time_millis_ wall clock,
                    // and that is time_predicted_percentage_ percent.
                    // How much time will we need to complete 100%?
                    double total_wall_time = time_millis_ / time_predicted_percentage_;
                    // Naturally, that gives us the time remaining.
                    eta_sec = (total_wall_time - time_millis_) / 1e3;
                }

                const bool print_with_hours =
                    elapsed_sec >= 60 * 60 || eta_sec >= 60 * 60;

                double sec = -1;
                switch (*s) {
                case 'e':  // elapsed, seconds
                case 'w':  // elapsed, human-readable
                    sec = elapsed_sec;
                    break;
                case 'E':  // ETA, seconds
                case 'W':  // ETA, human-readable
                    sec = eta_sec;
                    break;
                }

                if (sec < 0)
                    snprintf(buf, sizeof(buf), "?");
                else {
                    switch (*s) {
                    case 'e':  // elapsed, seconds
                    case 'E':  // ETA, seconds
                        snprintf(buf, sizeof(buf), "%.3f", sec);
                        break;
                    case 'w':  // elapsed, human-readable
                    case 'W':  // ETA, human-readable
                        if (print_with_hours)
                            snprintf(buf, sizeof(buf), FORMAT_TIME_HMMSS((int64_t)sec));
                        else
                            snprintf(buf, sizeof(buf), FORMAT_TIME_MMSS((int64_t)sec));
                        break;
                    }
                }
                out += buf;
                break;
            }

            // Percentage of time spent out of the predicted time total
            case 'P': {
                snprintf(buf, sizeof(buf), "%3i%%",
                    (int)(100. * time_predicted_percentage_));
                out += buf;
                break;
            }

            default:
                Fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *s);
                return "";
            }
        } else {
            out.push_back(*s);
        }
    }

    return out;
}

void StatusPrinter::PrintStatus(const Edge* edge, int64_t time_millis) {
    if (config_.verbosity == BuildConfig::QUIET
        || config_.verbosity == BuildConfig::NO_STATUS_UPDATE)
        return;

    RecalculateProgressPrediction();

    bool force_full_command = config_.verbosity == BuildConfig::VERBOSE;

    string to_print = edge->GetBinding("description");
    if (to_print.empty() || force_full_command)
        to_print = edge->GetBinding("command");

    to_print = FormatProgressStatus(progress_status_format_, time_millis)
        + to_print;

    printer_.Print(to_print,force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
}

void StatusPrinter::Warning(const char* msg, ...) {
    va_list ap;
    va_start(ap, msg);
    ::Warning(msg, ap);
    va_end(ap);
}

void StatusPrinter::Error(const char* msg, ...) {
    va_list ap;
    va_start(ap, msg);
    ::Error(msg, ap);
    va_end(ap);
}

void StatusPrinter::Info(const char* msg, ...) {
    va_list ap;
    va_start(ap, msg);
    ::Info(msg, ap);
    va_end(ap);
}
